package vin.pth.authentication.filter;

import javax.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import pth.authentication.config.AuthenticationProperties;
import pth.authentication.context.RbacContext;
import pth.authentication.service.AuthenticationUserService;
import pth.authentication.service.RbacService;
import reactor.core.publisher.Mono;
import vin.pth.authentication.handler.AuthenticationHandler;
import vin.pth.base.context.UserDetailsContext;
import vin.pth.base.exception.authentication.AuthenticationException;
import vin.pth.base.pojo.DefaultUserDetails;
import vin.pth.base.pojo.UserDetails;

/**
 * @author Cocoon
 */
@RequiredArgsConstructor
@Slf4j
public class AuthenticationFilter implements WebFilter {

  private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

  private final AuthenticationUserService authenticationUserService;
  private final RbacService rbacService;
  private final AuthenticationHandler authenticationHandler;
  @Resource
  private AuthenticationProperties authenticationProperties;

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    ServerHttpRequest httpRequest = exchange.getRequest();
    try {
      // 校验包含在白名单列表中
      if (!containsWhiteList(httpRequest.getPath().toString())) {
        UserDetails userDetails = DefaultUserDetails.anonymousUser();
        RbacContext rbacContext = buildRbacContext(httpRequest);
        var token = httpRequest.getHeaders().getFirst(authenticationProperties.getTokenName());
        if (!StringUtils.hasText(authenticationProperties.getTokenName())) {
          rbacContext.setToken(token);
          userDetails = authenticationUserService.getByToken(rbacContext.getToken());
          rbacContext.setUserDetails(userDetails);
        }
        rbacContext.setUserDetails(userDetails);
        rbacService.checkPermission(rbacContext);
        UserDetailsContext.setUserDetails(userDetails);
      }
      return chain.filter(exchange);
    } catch (AuthenticationException e) {
      return authenticationHandler.failureHandler(httpRequest, exchange.getResponse(), e);
    } finally {
      // 无论过程如何，都需要清理掉当前线程变量中的用户信息
      UserDetailsContext.clear();
    }
  }

  private RbacContext buildRbacContext(ServerHttpRequest request) {
    RbacContext context = new RbacContext();
    context.setUrl(request.getURI().toString());
    context.setPath(request.getPath().toString());
    context.setMethod(request.getMethodValue());
    context.setExtra(request);
    return context;
  }

  /**
   * 是否包含于白名单.
   *
   * @param uri URI
   * @return true=包含
   */
  private boolean containsWhiteList(String uri) {
    if (!CollectionUtils.isEmpty(authenticationProperties.getWhiteList())) {
      for (String s : authenticationProperties.getWhiteList()) {
        if (ANT_PATH_MATCHER.match(s, uri)) {
          log.info("{}:路径属于白名单路径{}，放行", uri, s);
          return true;
        }
      }
    }
    return false;
  }

}
